From 67da244065d3617bdd766f8bda3063b7584920ab Mon Sep 17 00:00:00 2001 From: oliskoli Date: Wed, 17 Sep 2008 21:18:50 +0000 Subject: [PATCH] exif: Add code to become an EXIF writer (JPEG images). --- exif.c | 1590 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 1237 insertions(+), 353 deletions(-) diff --git a/exif.c b/exif.c index d81e210fa..ce8a7a990 100644 --- a/exif.c +++ b/exif.c @@ -1,7 +1,7 @@ /* - Support for embedded Exif-GPS information. - + Support for embedded (JPEG) Exif-GPS information. + Copyright (C) 2008 Olaf Klein, o.b.klein@gpsbabel.org This program is free software; you can redistribute it and/or modify @@ -20,471 +20,1355 @@ */ +#include +#include +#include +#include + #include "defs.h" #include "config.h" #include "garmin_tables.h" #include "jeeps/gpsmath.h" #include "strptime.h" -#include #define MYNAME "exif" +// #define EXIF_DBG + #define UNKNOWN_TIMESTAMP 999999999 +#define IFD0 0 +#define IFD1 1 +#define EXIF_IFD 2 /* dummy index */ +#define GPS_IFD 3 /* dummy index */ +#define INTER_IFD 4 /* dummy index */ + +#define EXIF_TYPE_BYTE 1 +#define EXIF_TYPE_ASCII 2 +#define EXIF_TYPE_SHORT 3 +#define EXIF_TYPE_LONG 4 +#define EXIF_TYPE_RAT 5 +/* TIFF 6.0 */ +#define EXIF_TYPE_SBYTE 6 +#define EXIF_TYPE_UNK 7 +#define EXIF_TYPE_SSHORT 8 +#define EXIF_TYPE_SLONG 9 +#define EXIF_TYPE_SRAT 10 +#define EXIF_TYPE_FLOAT 11 +#define EXIF_TYPE_DOUBLE 12 +#define EXIF_TYPE_IFD 13 +#define EXIF_TYPE_UNICODE 14 +#define EXIF_TYPE_COMPLEX 15 +#define EXIF_TYPE_LONG8 16 +#define EXIF_TYPE_SLONG8 17 +#define EXIF_TYPE_IFD8 18 + +#define BYTE_TYPE(a) ( (a==EXIF_TYPE_BYTE) || (a==EXIF_TYPE_ASCII) || (a==EXIF_TYPE_UNK) ) +#define WORD_TYPE(a) ( (a==EXIF_TYPE_SHORT) || (a==EXIF_TYPE_SSHORT) ) +#define LONG_TYPE(a) ( (a==EXIF_TYPE_LONG) || (a==EXIF_TYPE_SLONG) || (a==EXIF_TYPE_IFD) ) + +#define IFD0_TAG_EXIF_IFD_OFFS 0x8769 +#define IFD0_TAG_GPS_IFD_OFFS 0x8825 + +#define IFD1_TAG_STRIP_OFFS 0x0111 +#define IFD1_TAG_JPEG_OFFS 0x0201 +#define IFD1_TAG_JPEG_SIZE 0x0202 + +#define EXIF_IFD_TAG_USER_CMT 0x9286 +#define EXIF_IFD_TAG_INTER_IFD_OFFS 0xA005 + +#define GPS_IFD_TAG_VERSION 0x0000 +#define GPS_IFD_TAG_LATREF 0x0001 +#define GPS_IFD_TAG_LAT 0x0002 +#define GPS_IFD_TAG_LONREF 0x0003 +#define GPS_IFD_TAG_LON 0x0004 +#define GPS_IFD_TAG_ALTREF 0x0005 +#define GPS_IFD_TAG_ALT 0x0006 +#define GPS_IFD_TAG_TIMESTAMP 0x0007 +#define GPS_IFD_TAG_SAT 0x0008 +#define GPS_IFD_TAG_MODE 0x000A +#define GPS_IFD_TAG_DOP 0x000B +#define GPS_IFD_TAG_SPEEDREF 0x000C +#define GPS_IFD_TAG_SPEED 0x000D +#define GPS_IFD_TAG_DATUM 0x0012 +#define GPS_IFD_TAG_DATESTAMP 0x001D + typedef struct exif_tag_s { - gbuint16 tag; + queue Q; + gbuint16 id; gbuint16 type; - gbint32 count; + gbuint32 count; + gbuint32 value; + gbuint32 origin; + gbuint32 size; +#ifdef EXIF_DBG gbuint32 offs; +#endif + unsigned char data_is_dynamic:1; + void *data; } exif_tag_t; -static gbfile *fin; -static gbsize_t exif_ifd, gps_ifd; -static char byte_order; -static waypoint *wpt; -static gbsize_t fileoffs; -static char *opt_filename; -static time_t timestamp; - -static +typedef struct exif_ifd_s { + queue Q; + gbuint32 next_ifd; + gbuint16 nr; + gbuint16 count; + queue tags; +} exif_ifd_t, *exif_ifd_p; + +typedef struct exif_app_s { + queue Q; + gbuint16 marker; + gbsize_t len; + gbfile *fcache; + gbfile *fexif; + queue ifds; +} exif_app_t; + +static gbfile *fin, *fout; +static queue exif_apps; +static exif_app_t *exif_app; +const waypoint *exif_wpt_ref; +time_t exif_time_ref; +static char exif_success; +static char *exif_fout_name; + +static char *opt_filename, *opt_nobackup, *opt_frame, *opt_name; + arglist_t exif_args[] = { - {"filename", &opt_filename, "Set waypoint name to source filename.", "Y", ARGTYPE_BOOL, ARG_NOMINMAX}, + { "filename", &opt_filename, "Set waypoint name to source filename.", "Y", ARGTYPE_BOOL, ARG_NOMINMAX }, + { "frame", &opt_frame, "Time-frame (in seconds).", "10", ARGTYPE_INT, "0", NULL }, + { "name", &opt_name, "Locate waypoint by this name.", NULL, ARGTYPE_STRING, ARG_NOMINMAX }, + { "nobackup", &opt_nobackup, "!OVERWRITE! the original file. Default 0=no.", "N", ARGTYPE_BOOL, ARG_NOMINMAX }, ARG_TERMINATOR }; -#define EXIF_IFD -1 -#define GPS_IFD -2 - -/******************************************************************************* -* %%% global callbacks called by gpsbabel main process %%% * -*******************************************************************************/ - +#ifdef EXIF_DBG static void -exif_rd_init(const char *fname) +print_buff(const char *buf, int sz, const char *cmt) { - fin = gbfopen_le(fname, "rb", MYNAME); + int i; + + printf("%s: ", cmt); + for (i = 0; i < sz; i++) + printf("%02x ", buf[i] & 0xFF); + for (i = 0; i < sz; i++) { + char c = buf[i]; + if isspace(c) c = ' '; + else if (! isprint(c)) c = '.'; + printf("%c", c); + } } +#endif -static void -exif_rd_deinit(void) +static gbuint16 +exif_type_size(const gbuint16 type) { - gbfclose(fin); -} + gbuint16 size; + + switch(type) { + case EXIF_TYPE_BYTE: + case EXIF_TYPE_ASCII: + case EXIF_TYPE_UNK: size = 1; break; + + case EXIF_TYPE_SHORT: + case EXIF_TYPE_SSHORT: + case EXIF_TYPE_UNICODE: size = 2; break; + case EXIF_TYPE_IFD: + case EXIF_TYPE_LONG: + case EXIF_TYPE_SLONG: + case EXIF_TYPE_FLOAT: size = 4; break; + + case EXIF_TYPE_RAT: + case EXIF_TYPE_SRAT: + case EXIF_TYPE_DOUBLE: + case EXIF_TYPE_LONG8: + case EXIF_TYPE_SLONG8: + case EXIF_TYPE_IFD8: size = 8; break; -#if 0 -static int -exif_tag_size(const exif_tag_t *tag) -{ - int size; - - switch(tag->type) { - case 1: - case 2: - case 7: size = 1; break; - case 3: size = 2; break; - case 4: - case 9: size = 4; break; - case 5: - case 10: size = 8; break; default: - return 0; + fatal(MYNAME ": Unknown data type %d! Please report.\n", type); } - return size * tag->count; + return size; +} + +static char * +exif_time_str(const time_t time) +{ + struct tm tm; + char *res; + + tm = *localtime(&time); + tm.tm_year += 1900; + tm.tm_mon += 1; + xasprintf(&res, "%04d/%02d/%02d, %02d:%02d:%02d", + tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + return res; +} + +static char * +exif_read_str(exif_tag_t *tag) +{ + return xstrndup((char *)tag->data, tag->size); } -#endif static double -exif_read_double(const gbsize_t offs) +exif_read_double(const exif_tag_t *tag, const int index) { unsigned int num, den; - - if (offs) gbfseek(fin, fileoffs + offs, SEEK_SET); - - num = gbfgetuint32(fin); - den = gbfgetuint32(fin); + gbint32 *data = (void *)tag->data; + + num = data[index * 2]; + den = data[(index * 2) + 1]; if (den == 0) den = 1; - - return (double)num / den; + + return (double)num / (double)den; } static double exif_read_coord(const exif_tag_t *tag) { - double deg, min, sec; - - deg = exif_read_double(tag->offs); - if (tag->count == 1) return deg; + double res, min, sec; - min = exif_read_double(0); - deg += (min / 60); - if (tag->count == 2) return deg; + res = exif_read_double(tag, 0); + if (tag->count == 1) return res; - sec = exif_read_double(0); - deg += (sec / 3600); + min = exif_read_double(tag, 1); + res += (min / 60); + if (tag->count == 2) return res; - return deg; -} + sec = exif_read_double(tag, 2); + res += (sec / 3600); -static char * -exif_read_string(const exif_tag_t *tag) -{ - if (tag->count > 4) { - gbfseek(fin, fileoffs + tag->offs, SEEK_SET); - return gbfgetcstr(fin); - } - else { - char buff[5]; - if (fin->big_endian) be_write32(buff, tag->offs); - else le_write32(buff, tag->offs); - if (tag->count < 5) buff[tag->count] = '\0'; - else buff[4] = '\0'; - return xstrdup(buff); - } + return res; } static time_t exif_read_timestamp(const exif_tag_t *tag) { double hour, min, sec; - - hour = exif_read_double(tag->offs); - min = exif_read_double(0); - sec = exif_read_double(0); - + + hour = exif_read_double(tag, 0); + min = exif_read_double(tag, 1); + sec = exif_read_double(tag, 2); + return ((int)hour * SECONDS_PER_HOUR) + ((int)min * 60) + (int)sec; } -static int -exif_sort_tags_cb(const void *a, const void *b) +static time_t +exif_read_datestamp(const exif_tag_t *tag) { - const exif_tag_t *ea = a; - const exif_tag_t *eb = b; - return (int)ea->offs - (int)eb->offs; + struct tm tm; + char *str; + + memset(&tm, 0, sizeof(tm)); + str = xstrndup((char *)tag->data, tag->size); + sscanf(str, "%d:%d:%d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday); + xfree(str); + + tm.tm_year -= 1900; + tm.tm_mon -= 1; + + return mkgmtime(&tm); } -static gbsize_t -exif_read_tags(const int ifd) +static void +exif_release_tag(exif_tag_t *tag) { - int entries; - exif_tag_t *tags = NULL; - gbsize_t next_ifd = 0; - double gpsdop = unknown_alt; - char speed_ref = 'K'; - char lat_ref = '*'; - char lon_ref = '*'; - char mode = 'N'; - int datum = DATUM_WGS84; - - entries = gbfgetint16(fin); - if (entries > 0) { - int i; - tags = xmalloc(entries * sizeof(*tags)); - for (i = 0; i < entries; i++) { - tags[i].tag = gbfgetuint16(fin); - tags[i].type = gbfgetuint16(fin); - tags[i].count = gbfgetint32(fin); - tags[i].offs = gbfgetuint32(fin); - } - } - - next_ifd = gbfgetuint32(fin); - - if (entries > 0) { - int i; - char *str, *c; - struct tm tm; + dequeue(&tag->Q); + if (tag->data_is_dynamic) xfree(tag->data); + xfree(tag); +} - if (entries > 1) /* avoid backward seek */ - qsort(tags, entries, sizeof(*tags), exif_sort_tags_cb); - - for (i = 0; i < entries; i++) { - exif_tag_t *tag = &tags[i]; - - switch(ifd) { - case 0: - switch(tag->tag) { - case 0x8769: - exif_ifd = tag->offs; - break; - case 0x8825: - gps_ifd = tag->offs; - break; - } - break; - case 1: /* IFD1 */ - break; - case -1: /* Exif */ - switch(tag->tag) { - - case 0x9003: /* DateTimeOriginal */ - str = exif_read_string(tag); - c = strptime(str, "%Y:%m:%d %H:%M:%S", &tm); - if (c && (*c == '\0')) - wpt->creation_time = mklocaltime(&tm); - xfree(str); - break; - } - break; - case -2: /* GPS */ - switch(tag->tag) { - - case 0x0001: /* GPSLatitudeRef */ - str = exif_read_string(tag); - lat_ref = *str & 127; - xfree(str); - break; - - case 0x0002: /* GPSLatitude */ - wpt->latitude = exif_read_coord(tag); - break; - - case 0x0003: /* GPSLongitudeRef */ - str = exif_read_string(tag); - lon_ref = *str & 127; - xfree(str); - break; - - case 0x0004: /* GPSLongitude */ - wpt->longitude = exif_read_coord(tag); - break; - - case 0x0005: /* GPSAltitudeRef */ - break; - - case 0x0006: /* GPSAltitude */ - wpt->altitude = exif_read_double(tag->offs); - break; - - case 0x0007: /* GPSTimeStamp */ - timestamp = exif_read_timestamp(tag); - break; - - case 0x0008: /* GPSSatellites */ - str = exif_read_string(tag); - wpt->sat = atoi(str); - xfree(str); - break; - - case 0x000a: /* GPSMeasureMode */ - str = exif_read_string(tag); - mode = *str & 127; - xfree(str); - break; - - case 0x000b: /* GPSDOP */ - gpsdop = exif_read_double(tag->offs); - break; - - case 0x000c: /* GPSSpeedRef */ - str = exif_read_string(tag); - speed_ref = *str & 127; - xfree(str); - break; - - case 0x000d: /* GPSSpeed */ - WAYPT_SET(wpt, speed, exif_read_double(tag->offs)); - break; - - case 0x0012: /* GPSMapDatum */ - str = exif_read_string(tag); - datum = gt_lookup_datum_index(str, MYNAME); - if (datum < 0) - fatal(MYNAME ": Unknown GPSMapDatum \"%s\"!\n", str); - xfree(str); - break; - } - break; +static void +exif_release_ifd(exif_ifd_t *ifd) +{ + if (ifd != NULL) { + queue *elem, *tmp; + + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + exif_release_tag((exif_tag_t *)elem); + } + xfree(ifd); + } +} + +static void +exif_release_apps(void) +{ + queue *e0, *t0; + + QUEUE_FOR_EACH(&exif_apps, e0, t0) { + queue *e1, *t1; + exif_app_t *app = (exif_app_t *)dequeue(e0); + + if (app->fcache) gbfclose(app->fcache); + if (app->fexif) gbfclose(app->fexif); + QUEUE_FOR_EACH(&app->ifds, e1, t1) { + exif_ifd_t *ifd = (exif_ifd_t *)dequeue(e1); + exif_release_ifd(ifd); + } + xfree(app); + } +} + +static gbuint32 +exif_ifd_size(exif_ifd_t *ifd) +{ + queue *elem, *tmp; + gbuint32 res = 6; /* nr of tags + next_ifd */ + + res += (ifd->count * 12); + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + exif_tag_t *tag = (exif_tag_t *)elem; + if (tag->size > 4) { + gbuint32 size = tag->size; + if (size & 1) size++; + res += size; + } + } + + return res; +} + +static exif_app_t * +exif_load_apps(void) +{ + exif_app_t *exif_app = NULL; + + while (! gbfeof(fin)) { + exif_app_t *app = xcalloc(sizeof(*app), 1); + + ENQUEUE_TAIL(&exif_apps, &app->Q); + QUEUE_INIT(&app->ifds); + app->fcache = gbfopen(NULL, "wb", MYNAME); + + app->marker = gbfgetuint16(fin); + app->len = gbfgetuint16(fin); +#ifdef EXIF_DBG + printf(MYNAME ": api = %02X, len = %d, offs = %04X\n", app->marker & 0xFF, app->len, gbftell(fin)); +#endif + if (exif_app || (app->marker == 0xFFDA)) /* compressed data */ { + gbfcopyfrom(app->fcache, fin, 0x7FFFFFFF); +#ifdef EXIF_DBG + printf(MYNAME ": compressed data size = %d\n", gbftell(app->fcache)); +#endif + } + else { + gbfcopyfrom(app->fcache, fin, app->len - 2); + if (app->marker == 0xFFE1) { + exif_app = app; } } - xfree(tags); } - if (ifd == GPS_IFD) { + return exif_app; +} + +static exif_ifd_t * +exif_read_ifd(exif_app_t *app, const gbuint16 ifd_nr, gbsize_t offs, + gbuint32 *exif_ifd_ofs, gbuint32 *gps_ifd_ofs, gbuint32 *inter_ifd_ofs) +{ + queue *elem, *tmp; + gbuint16 i; + exif_ifd_t *ifd; + gbfile *fin = app->fexif; + + ifd = xcalloc(sizeof(*ifd), 1); + QUEUE_INIT(&ifd->tags); + ENQUEUE_TAIL(&app->ifds, &ifd->Q); + ifd->nr = ifd_nr; - /* Did we get our minimum data ? */ - if ((wpt->latitude == unknown_alt) || (wpt->longitude == unknown_alt)) { - warning(MYNAME ": GPSLatitude and/or GPSLongitude not set!\n"); - waypt_free(wpt); - wpt = NULL; - return 0; + gbfseek(fin, offs, SEEK_SET); + ifd->count = gbfgetuint16(fin); + +#ifdef EXIF_DBG + { + char *name; + switch(ifd_nr) { + case IFD0: name = "IFD0"; break; + case IFD1: name = "IFD1"; break; + case GPS_IFD: name = "GPS"; break; + case EXIF_IFD: name = "EXIF"; break; + case INTER_IFD: name = "INTER"; break; + default: name = "private"; break; } - - if WAYPT_HAS(wpt, speed) { - switch(speed_ref) { - case 'K': - wpt->speed = KPH_TO_MPS(wpt->speed); - break; - case 'M': - wpt->speed = MPH_TO_MPS(wpt->speed); - break; - case 'N': - wpt->speed = KNOTS_TO_MPS(wpt->speed); - break; - default: - wpt->speed = 0; - WAYPT_UNSET(wpt, speed); - warning(MYNAME ": Unknown GPSSpeedRef unit %c (0x%02x)!\n", speed_ref, speed_ref); + printf(MYNAME "-offs 0x%04X: Number of items in IFD%d \"%s\" = %d (0x%2x)\n", + offs, ifd_nr, name, ifd->count, ifd->count); + } +#endif + if (ifd->count == 0) return ifd; + + for (i = 0; i < ifd->count; i++) { + exif_tag_t *tag; + offs = gbftell(fin); + + tag = xcalloc(sizeof(*tag), 1); + + ENQUEUE_TAIL(&ifd->tags, &tag->Q); + + tag->id = gbfgetuint16(fin); + tag->type = gbfgetuint16(fin); + tag->count = gbfgetuint32(fin); + tag->size = exif_type_size(tag->type) * tag->count; + tag->data = &tag->value; +#ifdef EXIF_DBG + tag->offs = offs; +#endif + + if (BYTE_TYPE(tag->type) && (tag->count <= 4)) { + gbfread(tag->data, 4, 1, fin); + } + else { + tag->value = gbfgetuint32(fin); + tag->origin = tag->value; + } + + if (ifd_nr == IFD0) { + if (tag->id == IFD0_TAG_EXIF_IFD_OFFS) *exif_ifd_ofs = tag->value; + else if (tag->id == IFD0_TAG_GPS_IFD_OFFS) *gps_ifd_ofs = tag->value; + } + else if (ifd_nr == EXIF_IFD) { + if (tag->id == EXIF_IFD_TAG_INTER_IFD_OFFS) *inter_ifd_ofs = tag->value; + } + } + + ifd->next_ifd = gbfgetuint16(fin); + + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + exif_tag_t *tag = (exif_tag_t *)elem; + if ((tag->size > 4) && (tag->value)) { + gbuint16 i; + void *ptr; + + tag->data = xmalloc(tag->size); + tag->data_is_dynamic = 1; + + ptr = (void *)tag->data; + gbfseek(fin, tag->value, SEEK_SET); + + if (BYTE_TYPE(tag->type)) gbfread(ptr, tag->count, 1, fin); + else for (i = 0; i < tag->count; i++) { + switch(tag->type) { + case EXIF_TYPE_SHORT: + case EXIF_TYPE_SSHORT: + *(gbint16 *)ptr = gbfgetuint16(fin); + break; + case EXIF_TYPE_IFD: + case EXIF_TYPE_LONG: + case EXIF_TYPE_SLONG: + *(gbint32 *)ptr = gbfgetuint32(fin); + break; + case EXIF_TYPE_RAT: + case EXIF_TYPE_SRAT: + *(gbint32 *)ptr = gbfgetuint32(fin); + *(gbint32 *)(ptr+4) = gbfgetuint32(fin); + break; + case EXIF_TYPE_FLOAT: + *(float *)ptr = gbfgetflt(fin); + break; + case EXIF_TYPE_DOUBLE: + *(double *)ptr = gbfgetdbl(fin); + break; + default: + gbfread(ptr, 1, 1, fin); + break; + } + ptr += (tag->size / tag->count); } } - - if (lat_ref == 'S') wpt->latitude *= -1; - else if (lat_ref != 'N') warning(MYNAME ": GPSLatitudeRef not set! Using N(orth).\n"); - if (lon_ref == 'W') wpt->longitude *= -1; - else if (lon_ref != 'E') warning(MYNAME ": GPSLongitudeRef not set! Using E(east).\n"); - - if (datum != DATUM_WGS84) { +#ifdef EXIF_DBG + printf(MYNAME "-offs 0x%04X: ifd=%d id=0x%04X t=0x%04X c=%4d s=%4d v=0x%08X", + tag->offs, ifd->nr, tag->id, tag->type, tag->count, tag->size, tag->value); + if (tag->type == EXIF_TYPE_ASCII) printf(" \"%s\"", exif_read_str(tag)); + printf("\n"); +#endif + } + + return ifd; +} + +static void +exif_read_app(exif_app_t *app) +{ + gbsize_t offs; + gbuint32 exif_ifd_ofs, gps_ifd_ofs, inter_ifd_ofs; + exif_ifd_t *ifd; + gbfile *fin = app->fexif; + +#ifdef EXIF_DBG + printf(MYNAME ": read_app...\n"); + print_buff((const char *)fin->handle.mem, 16, MYNAME); + printf("\n"); +#endif + exif_ifd_ofs = gps_ifd_ofs = inter_ifd_ofs = 0; + + gbfseek(fin, 4, SEEK_SET); + offs = gbfgetuint32(fin); + + ifd = exif_read_ifd(app, IFD0, offs, &exif_ifd_ofs, &gps_ifd_ofs, &inter_ifd_ofs); + if (ifd == NULL) return; + + if (ifd->next_ifd) + ifd = exif_read_ifd(app, IFD1, ifd->next_ifd, &exif_ifd_ofs, &gps_ifd_ofs, &inter_ifd_ofs); + if (exif_ifd_ofs) + ifd = exif_read_ifd(app, EXIF_IFD, exif_ifd_ofs, NULL, NULL, &inter_ifd_ofs); + if (gps_ifd_ofs) + ifd = exif_read_ifd(app, 3, gps_ifd_ofs, NULL, NULL, NULL); + if (inter_ifd_ofs) + ifd = exif_read_ifd(app, 4, inter_ifd_ofs, NULL, NULL, NULL); +} + +static void +exif_examine_app(exif_app_t *app) +{ + gbuint16 endianess; + gbuint32 ident; + gbfile *ftmp = exif_app->fcache; + int i; + + gbfrewind(ftmp); + ident = gbfgetuint32(ftmp); + is_fatal(ident != 0x66697845, MYNAME ": Invalid EXIF header magic."); + is_fatal(gbfgetint16(ftmp) != 0, MYNAME ": Error in EXIF header."); + endianess = gbfgetint16(ftmp); + +#ifdef EXIF_DBG + printf(MYNAME ": endianess = 0x%04X\n", endianess); +#endif + if (endianess == 0x4949) ftmp->big_endian = 0; + else if (endianess == 0x4D4D) ftmp->big_endian = 1; + else fatal(MYNAME ": Invalid endianess identifier 0x%04X!\n", endianess); + + gbfseek(ftmp, 6, SEEK_SET); + app->fexif = gbfopen(NULL, "wb", MYNAME); + app->fexif->big_endian = ftmp->big_endian; + i = gbfcopyfrom(app->fexif, ftmp, 0x7FFFFFFF); + + exif_read_app(exif_app); +} + +static exif_ifd_t * +exif_find_ifd(exif_app_t *app, const gbuint16 ifd_nr) +{ + queue *e0, *t0; + + QUEUE_FOR_EACH(&app->ifds, e0, t0) { + exif_ifd_t *ifd = (exif_ifd_t *)e0; + + if (ifd->nr == ifd_nr) return ifd; + } + return NULL; +} + +static exif_tag_t * +exif_find_tag(exif_app_t *app, const gbuint16 ifd_nr, const gbuint16 tag_id) +{ + exif_ifd_t *ifd = exif_find_ifd(app, ifd_nr); + if (ifd != NULL) { + queue *elem, *tmp; + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + exif_tag_t *tag = (exif_tag_t *)elem; + if (tag->id == tag_id) return tag; + } + } + return NULL; +} + +static time_t +exif_get_exif_time(exif_app_t *app) +{ + exif_tag_t *tag; + time_t res = 0; + + tag = exif_find_tag(app, EXIF_IFD, 0x9003); /* DateTimeOriginal from EXIF */ + if (! tag) tag = exif_find_tag(app, IFD0, 0x0132); /* DateTime from IFD0 */ + if (! tag) tag = exif_find_tag(app, EXIF_IFD, 0x9004); /* DateTimeDigitized from EXIF */ + if (tag) { + struct tm tm; + char *c, *str; + + memset(&tm, 0, sizeof(tm)); + str = exif_read_str(tag); + c = strptime(str, "%Y:%m:%d %H:%M:%S", &tm); + if (c && (*c == '\0')) res = mklocaltime(&tm); + + xfree(str); + } + return res; +} + +static waypoint * +exif_waypt_from_exif_app(exif_app_t *app) +{ + waypoint *wpt; + queue *elem, *tmp; + exif_ifd_t *ifd; + exif_tag_t *tag; + char lat_ref = '\0'; + char lon_ref = '\0'; + char alt_ref = 0; + char speed_ref = '\0'; + char *datum = NULL; + char mode = '\0'; + double gpsdop = unknown_alt; + double alt = unknown_alt; + time_t timestamp = UNKNOWN_TIMESTAMP; + time_t datestamp = UNKNOWN_TIMESTAMP; + + ifd = exif_find_ifd(app, GPS_IFD); + if (ifd == NULL) return NULL; + + wpt = waypt_new(); + + wpt->latitude = unknown_alt; + wpt->longitude = unknown_alt; + + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + tag = (exif_tag_t *)elem; + + switch(tag->id) { + case GPS_IFD_TAG_VERSION: break; + case GPS_IFD_TAG_LATREF: lat_ref = *(char *)tag->data; break; + case GPS_IFD_TAG_LAT: wpt->latitude = exif_read_coord(tag); break; + case GPS_IFD_TAG_LONREF: lon_ref = *(char *)tag->data; break; + case GPS_IFD_TAG_LON: wpt->longitude = exif_read_coord(tag); break; + case GPS_IFD_TAG_ALTREF: alt_ref = *(char *)tag->data; break; + case GPS_IFD_TAG_ALT: alt = exif_read_double(tag, 0); break; + case GPS_IFD_TAG_TIMESTAMP: timestamp = exif_read_timestamp(tag); break; + case GPS_IFD_TAG_SAT: wpt->sat = atoi((char *)tag->data); break; + case GPS_IFD_TAG_MODE: mode = *(char *)tag->data; break; + case GPS_IFD_TAG_DOP: gpsdop = exif_read_double(tag, 0); break; + case GPS_IFD_TAG_SPEEDREF: speed_ref = *(char *)tag->data; break; + case GPS_IFD_TAG_SPEED: WAYPT_SET(wpt, speed, exif_read_double(tag, 0)); break; + case GPS_IFD_TAG_DATUM: datum = exif_read_str(tag); break; + case GPS_IFD_TAG_DATESTAMP: datestamp = exif_read_datestamp(tag); break; + } + } + + if ((wpt->latitude == unknown_alt) || (wpt->longitude == unknown_alt)) + fatal(MYNAME ": Missing GPSLatitude and/or GPSLongitude!\n"); + + if (lat_ref == 'S') + wpt->latitude *= -1; + else if (lat_ref != 'N') + warning(MYNAME ": GPSLatitudeRef not set! Using N(orth).\n"); + + if (lon_ref == 'W') + wpt->longitude *= -1; + else if (lon_ref != 'E') + warning(MYNAME ": GPSLongitudeRef not set! Using E(east).\n"); + +#ifdef EXIF_DBG + printf(MYNAME "-GPSLatitude = %12.7f\n", wpt->latitude); + printf(MYNAME "-GPSLongitude = %12.7f\n", wpt->longitude); +#endif + if (datum) { + int idatum = gt_lookup_datum_index(datum, MYNAME); + if (idatum < 0) + fatal(MYNAME ": Unknown GPSMapDatum \"%s\"!\n", datum); + if (idatum != DATUM_WGS84) { double alt; GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0.0, - &wpt->latitude, &wpt->longitude, &alt, datum); + &wpt->latitude, &wpt->longitude, &alt, idatum); + } + xfree(datum); + } + + if (alt != unknown_alt) { + if (alt_ref != 0) + warning(MYNAME ": Invalid GPSAltitudeRef (%d)! Using 0 (= Sea level).\n", alt_ref); + wpt->altitude = alt; +#ifdef EXIF_DBG + printf(MYNAME "-GPSAltitude = %12.7f m\n", wpt->altitude); +#endif + } + + if WAYPT_HAS(wpt, speed) { + switch(speed_ref) { + case 'K': + wpt->speed = KPH_TO_MPS(wpt->speed); + break; + case 'M': + wpt->speed = MPH_TO_MPS(wpt->speed); + break; + case 'N': + wpt->speed = KNOTS_TO_MPS(wpt->speed); + break; + default: + wpt->speed = 0; + WAYPT_UNSET(wpt, speed); + warning(MYNAME ": Unknown GPSSpeedRef unit %c (0x%02x)!\n", speed_ref, speed_ref); + } +#ifdef EXIF_DBG + if WAYPT_HAS(wpt, speed) printf(MYNAME "-GPSSpeed = %12.2f m/s\n", wpt->speed); +#endif + } + + if (mode == '2') { + wpt->fix = fix_2d; + if (gpsdop != unknown_alt) wpt->hdop = gpsdop; + } + else if (mode == '3') { + wpt->fix = fix_3d; + if (gpsdop != unknown_alt) wpt->pdop = gpsdop; + } + + if (timestamp != UNKNOWN_TIMESTAMP) { + if (datestamp != UNKNOWN_TIMESTAMP) timestamp += datestamp; + } + else timestamp = datestamp; + if (timestamp != UNKNOWN_TIMESTAMP) { +#ifdef EXIF_DBG + char *str = exif_time_str(timestamp); + printf(MYNAME "-GPSTimeStamp = %s\n", str); + xfree(str); +#endif + wpt->creation_time = timestamp; + } + else + wpt->creation_time = exif_get_exif_time(app); + + tag = exif_find_tag(app, EXIF_IFD, EXIF_IFD_TAG_USER_CMT); /* UserComment */ + if (tag && (tag->size > 8)) { + char *str = NULL; + if (memcmp(tag->data, "ASCII\0\0\0", 8) == 0) { + str = xstrndup(tag->data + 8, tag->size - 8); } - - if (mode == '2') { - wpt->fix = fix_2d; - if (gpsdop != unknown_alt) wpt->hdop = gpsdop; + else if (memcmp(tag->data, "UNICODE\0", 8) == 0) { + int i, len = (tag->size - 8) / 2; + gbint16 *s = tag->data + 8; + for (i = 0; i < len; i++) s[i] = be_read16(&s[i]); /* always BE ? */ + str = cet_str_uni_to_any(s, len, global_opts.charset); } - else if (mode == '3') { - wpt->fix = fix_3d; - if (gpsdop != unknown_alt) wpt->pdop = gpsdop; + if (str != NULL) { + wpt->notes = str; } } - return next_ifd; + if (opt_filename) { + char *c, *cx; + char *str = xstrdup(fin->name); + + cx = str; + if ((c = strrchr(cx, ':'))) cx = c + 1; + if ((c = strrchr(cx, '\\'))) cx = c + 1; + if ((c = strrchr(cx, '/'))) cx = c + 1; + if (((c = strchr(cx, '.'))) && (c != cx)) *c = '\0'; + + if (wpt->shortname) xfree(wpt->shortname); + wpt->shortname = xstrdup(cx); + xfree(str); + } + + return wpt; } static void -exif_read(void) +exif_dec2frac(double val, int *num, int *den) +{ + char sval[16], snum[16]; + char dot = 0; + int den1 = 1; + int num1, num2, den2, rem; + char *cx; + double vx; + + if (val < 0.000000001) val = 0.0; + else if (val > 999999999.0) + fatal(MYNAME ": Value (%f) to big for a rational representation!\n", val); + + num1 = 0; + vx = fabs(val); + while (vx > 1) { + num1++; + vx = vx / 10; + } + + snprintf(sval, sizeof(sval), "%*.*f", 9, 9 - num1, fabs(val)); + snum[0] = '\0'; + + cx = sval; + while (*cx) { + if (dot) den1 *= 10; + if (*cx == '.') dot = 1; + else strncat(snum, cx, 1); + cx++; + } + + num1 = atoi(snum); + if (den1 == 1) { + *num = num1; + *den = den1; + } + + num2 = num1; + den2 = den1; + rem = 1; + + /* Euclid's Algorithm to find the gcd */ + while (num2 % den2) { + rem = num2 % den2; + num2 = den2; + den2 = rem; + } + if (den2 != den1) rem = den2; + + *num = num1 / rem; + *den = den1 / rem; +} + +static exif_tag_t * +exif_put_value(const int ifd_nr, const gbuint16 tag_id, const gbuint16 type, const gbuint32 count, const int index, const void *data) { - gbint32 code = 0; - - fileoffs = 0; - wpt = NULL; - - while (!gbfeof(fin)) { - - unsigned char c = (unsigned)gbfgetc(fin); - - code = (code << 8) | c; - if (code == 0x45786966) { /* Look for "Exif" */ - - gbsize_t next_ifd; - gbint32 ifd; - - int order = gbfgetint32(fin); - switch(order) { - case 0x49490000: /* "II" - Intel */ - byte_order = 'I'; - break; - case 0x4D4D0000: /* "MM" - Motorola */ - byte_order = 'M'; - break; - - default: - continue; + exif_tag_t *tag = NULL; + exif_ifd_t *ifd; + gbuint16 item_size, size; + + ifd = exif_find_ifd(exif_app, ifd_nr); + if (ifd == NULL) { + ifd = xcalloc(sizeof(*ifd), 1); + ifd->nr = ifd_nr; + QUEUE_INIT(&ifd->tags); + ENQUEUE_TAIL(&exif_app->ifds, &ifd->Q); + } + else tag = exif_find_tag(exif_app, ifd_nr, tag_id); + + item_size = exif_type_size(type); + + if ((data == NULL) || (count < 1) || (index < 0)) size = 0; + else size = (index + count) * item_size; + + if (tag == NULL) { + if (size == 0) return NULL; + + tag = xcalloc(sizeof(*tag), 1); + + tag->id = tag_id; + tag->type = type; + tag->count = index + count; + tag->size = size; + tag->data = xcalloc((size < 4) ? 4 : size, 1); + tag->data_is_dynamic = 1; + ifd->count++; + + ENQUEUE_TAIL(&ifd->tags, &tag->Q); + } + else { + if (size == 0) { /* remove this element */ + ifd->count--; + exif_release_tag(tag); + return NULL; + } + if (tag->count < (index + count)) { + if (! tag->data_is_dynamic) { + void *tmp = xmalloc(tag->size < 4 ? 4 : tag->size); + memcpy(tmp, tag->data, tag->size); + tag->data = tmp; + tag->data_is_dynamic = 1; } - fin->big_endian = (byte_order == 'M'); - if (gbfgetint16(fin) != 0x002a) continue; - - fileoffs = gbftell(fin) - 4; - - next_ifd = gbfgetuint32(fin); - if (! next_ifd) continue; /* No IFD0 ? */ - - ifd = gps_ifd = exif_ifd = 0; - timestamp = UNKNOWN_TIMESTAMP; - - /* we need the wpt not only during GPS IFD */ - wpt = waypt_new(); - wpt->latitude = unknown_alt; - wpt->longitude = unknown_alt; - - while (next_ifd) { - gbfseek(fin, fileoffs + next_ifd, SEEK_SET); - next_ifd = exif_read_tags(ifd++); + tag->size = size; + tag->count = index + count; + tag->data = xrealloc(tag->data, size < 4 ? 4 : size); + } + } + + switch(type) { + case EXIF_TYPE_RAT: + case EXIF_TYPE_SRAT: { + double val = *(double *)data; + gbuint32 *dest = tag->data; + + if ((int)val == val) { + dest[index * 2] = (int)val; + dest[(index * 2) + 1] = 1; } - if (exif_ifd) { - gbfseek(fin, fileoffs + exif_ifd, SEEK_SET); - (void) exif_read_tags(EXIF_IFD); + else { + gbint32 Nom, Den; + exif_dec2frac(val, &Nom, &Den); + if ((type == EXIF_TYPE_SRAT) && (val < 0.0)) Nom *= -1; + dest[index * 2] = Nom; + dest[(index * 2) + 1] = Den; } - if (gps_ifd) { - gbfseek(fin, fileoffs + gps_ifd, SEEK_SET); - (void) exif_read_tags(GPS_IFD); } - else { - warning(MYNAME ": No Exif-GPS information in file \"%s\"!\n", fin->name); - waypt_free(wpt); - wpt = NULL; + break; + default: { + char *dest = tag->data; + memcpy(&dest[index * item_size], data, count * item_size); } - - if (! wpt) return; - - if (wpt->creation_time && (timestamp != UNKNOWN_TIMESTAMP)) { - struct tm tm; - tm = *gmtime(&wpt->creation_time); - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - wpt->creation_time = mkgmtime(&tm) + timestamp; + } + return tag; +} + + +static void +exif_put_double(const int ifd_nr, const int tag_id, const int index, const double val) +{ + double d = fabs(val); + exif_put_value(ifd_nr, tag_id, EXIF_TYPE_RAT, 1, index, &d); +} + + +static void +exif_put_str(const int ifd_nr, const int tag_id, const char *val) +{ + int len = (val) ? strlen(val) + 1 : 0; + exif_put_value(ifd_nr, tag_id, EXIF_TYPE_ASCII, len, 0, val); +} + +static void +exif_put_coord(const int ifd_nr, const int tag_id, const double val) +{ + double vmin, vsec; + int vint; + + vint = abs((int) val); + vmin = 60.0 * (fabs(val) - vint); + vsec = 60.0 * (vmin - floor(vmin)); + + exif_put_double(ifd_nr, tag_id, 0, (double)vint); + exif_put_double(ifd_nr, tag_id, 1, (double)vmin); + exif_put_double(ifd_nr, tag_id, 2, (double)vsec); +} + +static void +exif_put_long(const int ifd_nr, const int tag_id, const int index, const gbint32 val) +{ + exif_put_value(ifd_nr, tag_id, EXIF_TYPE_LONG, 1, index, &val); +} + +static void +exif_remove_tag(const int ifd_nr, const int tag_id) +{ + exif_put_value(ifd_nr, tag_id, EXIF_TYPE_BYTE, 0, 0, NULL); +} + +static void +exif_find_wpt_by_time(const waypoint *wpt) +{ + if (wpt->creation_time <= 0) return; + + if (exif_wpt_ref == NULL) exif_wpt_ref = wpt; + else if (abs(exif_time_ref - wpt->creation_time) < abs(exif_time_ref - exif_wpt_ref->creation_time)) + exif_wpt_ref = wpt; +} + +static void +exif_find_wpt_by_name(const waypoint *wpt) +{ + if (exif_wpt_ref != NULL) + return; + else if ((wpt->shortname != NULL) && (case_ignore_strcmp(wpt->shortname, opt_name) == 0)) + exif_wpt_ref = wpt; +} + + +static int +exif_sort_tags_cb(const queue *A, const queue *B) +{ + exif_tag_t *ta = (exif_tag_t *)A; + exif_tag_t *tb = (exif_tag_t *)B; + + return ta->id - tb->id; +} + +static int +exif_sort_ifds_cb(const queue *A, const queue *B) +{ + exif_ifd_t *ia = (exif_ifd_t *)A; + exif_ifd_t *ib = (exif_ifd_t *)B; + + return ia->nr - ib->nr; +} + +static void +exif_write_value(exif_tag_t *tag, gbfile *fout) +{ + if (tag->size > 4) gbfputuint32(tag->value, fout); /* offset to data */ + else { + void *data = tag->data; + + if BYTE_TYPE(tag->type) gbfwrite(data, 4, 1, fout); + else if WORD_TYPE(tag->type) { + gbfputuint16(*(gbuint16 *)data, fout); + gbfputuint16(*(gbuint16 *)(data+2), fout); + } + else if LONG_TYPE(tag->type) gbfputuint32(*(gbuint32 *)data, fout); + else if (tag->type == EXIF_TYPE_FLOAT) gbfputflt(*(float *)data, fout); + else fatal(MYNAME ": Unknown data type %d!\n", tag->type); + } +} + +static void +exif_write_ifd(const exif_ifd_t *ifd, const char next, gbfile *fout) +{ + gbsize_t offs; + queue *elem, *tmp; + + gbfputuint16(ifd->count, fout); + offs = gbftell(fout) + (ifd->count * 12) + 4; + + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + exif_tag_t *tag = (exif_tag_t *)elem; + + gbfputuint16(tag->id, fout); + gbfputuint16(tag->type, fout); + gbfputuint32(tag->count, fout); + if (tag->size > 4) { + tag->value = offs; + offs += tag->size; + if (offs & 1) offs++; + gbfputuint32(tag->value, fout); + } + else exif_write_value(tag, fout); + } + + if (next) gbfputuint32(offs, fout); + else gbfputuint32(0, fout); + + QUEUE_FOR_EACH(&ifd->tags, elem, tmp) { + exif_tag_t *tag = (exif_tag_t *)elem; + + if (tag->size > 4) { + gbuint16 i; + void *ptr = tag->data; + + if BYTE_TYPE(tag->type) gbfwrite(tag->data, tag->size, 1, fout); + else for (i = 0; i < tag->count; i++) { + switch(tag->type) { + case EXIF_TYPE_SHORT: + case EXIF_TYPE_SSHORT: + gbfputuint16(*(gbint16 *)ptr, fout); + break; + case EXIF_TYPE_LONG: + case EXIF_TYPE_SLONG: + case EXIF_TYPE_IFD: + gbfputuint32(*(gbint32 *)ptr, fout); + break; + case EXIF_TYPE_RAT: + case EXIF_TYPE_SRAT: + gbfputuint32(*(gbint32 *)ptr, fout); + gbfputuint32(*(gbint32 *)(ptr+4), fout); + break; + case EXIF_TYPE_FLOAT: + gbfputflt(*(float *)ptr, fout); + break; + case EXIF_TYPE_DOUBLE: + gbfputdbl(*(double *)ptr, fout); + break; + default: + gbfwrite(ptr, exif_type_size(tag->type), 1, fin); + break; + } + ptr += (tag->size / tag->count); } + if (gbftell(fout) & 1) gbfputc(0, fout); + } + } +} - if (opt_filename) { - char *c, *cx; - char *str = xstrdup(fin->name); +static void +exif_write_apps(void) +{ + queue *e0, *t0; - cx = str; - if ((c = strrchr(cx, ':'))) cx = c + 1; - if ((c = strrchr(cx, '\\'))) cx = c + 1; - if ((c = strrchr(cx, '/'))) cx = c + 1; - if (((c = strchr(cx, '.'))) && (c != cx)) *c = '\0'; + gbfputuint16(0xFFD8, fout); - if (wpt->shortname) xfree(wpt->shortname); - wpt->shortname = xstrdup(cx); - xfree(str); + QUEUE_FOR_EACH(&exif_apps, e0, t0) { + exif_app_t *app = (exif_app_t *)e0; + + gbfputuint16(app->marker, fout); + + if (app == exif_app) { + queue *e1, *t1; + gbuint16 len = 8; + gbfile *ftmp; + exif_tag_t *tag; + + exif_put_long(IFD0, IFD0_TAG_GPS_IFD_OFFS, 0, 0); + exif_put_long(GPS_IFD, GPS_IFD_TAG_VERSION, 0, 2); + + sortqueue(&exif_app->ifds, exif_sort_ifds_cb); + + QUEUE_FOR_EACH(&app->ifds, e1, t1) { + exif_ifd_t *ifd = (exif_ifd_t *)e1; + + if (ifd->nr == GPS_IFD) exif_put_long(IFD0, IFD0_TAG_GPS_IFD_OFFS, 0, len); + else if (ifd->nr == EXIF_IFD) exif_put_long(IFD0, IFD0_TAG_EXIF_IFD_OFFS, 0, len); + else if (ifd->nr == INTER_IFD) exif_put_long(EXIF_IFD, EXIF_IFD_TAG_INTER_IFD_OFFS, 0, len); + + len += exif_ifd_size(ifd); } - waypt_add(wpt); - - return; + + len += 4; /* DWORD(0) after last ifd */ + + if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS))) + exif_put_long(IFD1, IFD1_TAG_JPEG_OFFS, 0, len); + + QUEUE_FOR_EACH(&app->ifds, e1, t1) { + exif_ifd_t *ifd = (exif_ifd_t *)e1; + sortqueue(&ifd->tags, exif_sort_tags_cb); + } + + ftmp = gbfopen_be(NULL, "wb", MYNAME); + ftmp->big_endian = app->fcache->big_endian; + + gbfwrite((ftmp->big_endian) ? "MM" : "II", 2, 1, ftmp); + gbfputuint16(0x2A, ftmp); + gbfputuint32(0x08, ftmp); /* offset to first IFD */ + + QUEUE_FOR_EACH(&app->ifds, e1, t1) { + exif_ifd_t *ifd = (exif_ifd_t *)e1; + exif_ifd_t *ifd_next = (exif_ifd_t *)t1; + char next; + + if ((ifd->nr == IFD0) && (ifd_next->nr == IFD1)) next = 1; + else next = 0; + + exif_write_ifd(ifd, next, ftmp); + len = gbftell(ftmp); + } + + gbfputuint32(0, ftmp); /* DWORD(0) after last ifd */ + + if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_OFFS))) { + gbsize_t offs = tag->origin; + if ((tag = exif_find_tag(app, IFD1, IFD1_TAG_JPEG_SIZE))) { + gbfseek(app->fexif, offs, SEEK_SET); + gbfcopyfrom(ftmp, app->fexif, tag->value); + } + } + + len = gbftell(ftmp); + gbfrewind(ftmp); + gbfputuint16(len + 8, fout); + gbfwrite("Exif\0\0", 6, 1, fout); + gbfcopyfrom(fout, ftmp, len); + + gbfclose(ftmp); + } + else { + gbfputuint16(app->len, fout); + gbfrewind(app->fcache); + gbfcopyfrom(fout, app->fcache, 0x7FFFFFFF); } } - warning(MYNAME ": No Exif header in file \"%s\"!\n", fin->name); } -#if 0 +/******************************************************************************* +* %%% global callbacks called by gpsbabel main process %%% * +*******************************************************************************/ + +static void +exif_rd_init(const char *fname) +{ + fin = gbfopen_be(fname, "rb", MYNAME); + QUEUE_INIT(&exif_apps); +} + +static void +exif_rd_deinit(void) +{ + exif_release_apps(); + gbfclose(fin); +} + +static void +exif_read(void) +{ + gbuint16 soi; + waypoint *wpt; + + soi = gbfgetuint16(fin); + is_fatal(soi != 0xFFD8, MYNAME ": Unknown image file."); /* only jpeg for now */ + + exif_app = exif_load_apps(); + is_fatal(exif_app == NULL, MYNAME ": No EXIF header in source file \"%s\".", fin->name); + + exif_examine_app(exif_app); + wpt = exif_waypt_from_exif_app(exif_app); + if (wpt) waypt_add(wpt); +} + static void exif_wr_init(const char *fname) { - fout = gbfopen(fname, "w", MYNAME); + gbuint16 soi; + char *tmpname; + + exif_success = 0; + exif_fout_name = xstrdup(fname); + + QUEUE_INIT(&exif_apps); + + fin = gbfopen_be(fname, "rb", MYNAME); + is_fatal(fin->is_pipe, MYNAME ": Sorry, this format cannot be used with pipes!"); + + soi = gbfgetuint16(fin); + is_fatal(soi != 0xFFD8, MYNAME ": Unknown image file."); + exif_app = exif_load_apps(); + is_fatal(exif_app == NULL, MYNAME ": No EXIF header found in source file \"%s\".", fin->name); + exif_examine_app(exif_app); + gbfclose(fin); + + exif_time_ref = exif_get_exif_time(exif_app); + if (exif_time_ref == 0) fatal(MYNAME ": No valid timestamp found in picture!\n"); + + xasprintf(&tmpname, "%s.jpg", fname); + fout = gbfopen_be(tmpname, "wb", MYNAME); + xfree(tmpname); } static void exif_wr_deinit(void) { + char *tmpname; + + exif_release_apps(); + tmpname = xstrdup(fout->name); gbfclose(fout); + + if (exif_success) { + if (*opt_nobackup == '1') { + remove(exif_fout_name); + rename(tmpname, exif_fout_name); + } + } + else remove(tmpname); + + xfree(exif_fout_name); + xfree(tmpname); } static void exif_write(void) { + exif_wpt_ref = NULL; + char alt_ref = 0; + time_t frame; + + if (opt_name) { + waypt_disp_all(exif_find_wpt_by_name); + if (exif_wpt_ref == NULL) route_disp_all(NULL, NULL, exif_find_wpt_by_name); + if (exif_wpt_ref == NULL) track_disp_all(NULL, NULL, exif_find_wpt_by_name); + if (exif_wpt_ref == NULL) { + warning(MYNAME ": No matching point with name \"%s\" found.\n", opt_name); + } + } + else { + char *str = exif_time_str(exif_time_ref); + + track_disp_all(NULL, NULL, exif_find_wpt_by_time); + route_disp_all(NULL, NULL, exif_find_wpt_by_time); + waypt_disp_all(exif_find_wpt_by_time); + + frame = atoi(opt_frame); + + if (exif_wpt_ref == NULL) + warning(MYNAME ": No point with a valid timestamp found.\n"); + else if (abs(exif_time_ref - exif_wpt_ref->creation_time) > frame) { + warning(MYNAME ": No matching point found for image date %s!\n", str); + if (exif_wpt_ref != NULL) { + char *str = exif_time_str(exif_wpt_ref->creation_time); + warning(MYNAME ": Best is from %s, %d second(s) away.\n", + str, abs(exif_time_ref - exif_wpt_ref->creation_time)); + xfree(str); + } + exif_wpt_ref = NULL; + } + xfree(str); + } + + if (exif_wpt_ref != NULL) { + const waypoint *wpt = exif_wpt_ref; + + exif_put_long(IFD0, IFD0_TAG_GPS_IFD_OFFS, 0, 0); + exif_put_long(GPS_IFD, GPS_IFD_TAG_VERSION, 0, 2); + exif_put_str(GPS_IFD, GPS_IFD_TAG_DATUM, "WGS-84"); + + exif_put_str(GPS_IFD, GPS_IFD_TAG_LATREF, wpt->latitude < 0 ? "S" : "N"); + exif_put_coord(GPS_IFD, GPS_IFD_TAG_LAT, fabs(wpt->latitude)); + exif_put_str(GPS_IFD, GPS_IFD_TAG_LONREF, wpt->longitude < 0 ? "W" : "E"); + exif_put_coord(GPS_IFD, GPS_IFD_TAG_LON, fabs(wpt->longitude)); + + if (wpt->altitude == unknown_alt) { + exif_remove_tag(GPS_IFD, GPS_IFD_TAG_ALT); + exif_remove_tag(GPS_IFD, GPS_IFD_TAG_ALTREF); + } + else { + exif_put_value(GPS_IFD, GPS_IFD_TAG_ALTREF, EXIF_TYPE_BYTE, 1, 0, &alt_ref); + exif_put_double(GPS_IFD, GPS_IFD_TAG_ALT, 0, wpt->altitude); + } + + if (wpt->creation_time) { + struct tm tm; + char buf[32]; + + tm = *gmtime(&wpt->creation_time); + tm.tm_year += 1900; + tm.tm_mon += 1; + + exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 0, tm.tm_hour); + exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 1, tm.tm_min); + exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2, tm.tm_sec); + + snprintf(buf, sizeof(buf), "%04d:%02d:%02d", tm.tm_year, tm.tm_mon, tm.tm_mday); + exif_put_str(GPS_IFD, GPS_IFD_TAG_DATESTAMP, buf); + } + else { + exif_remove_tag(GPS_IFD, GPS_IFD_TAG_TIMESTAMP); + exif_remove_tag(GPS_IFD, GPS_IFD_TAG_DATESTAMP); + } + + if (wpt->sat > 0) { + char buf[16]; + snprintf(buf, sizeof(buf), "%d", wpt->sat); + exif_put_str(GPS_IFD, GPS_IFD_TAG_SAT, buf); + } + else exif_remove_tag(GPS_IFD, GPS_IFD_TAG_SAT); + + if (wpt->fix == fix_2d) exif_put_str(GPS_IFD, GPS_IFD_TAG_MODE, "2"); + else if (wpt->fix == fix_3d) exif_put_str(GPS_IFD, GPS_IFD_TAG_MODE, "3"); + else exif_remove_tag(GPS_IFD, GPS_IFD_TAG_MODE); + + if (wpt->hdop > 0) exif_put_double(GPS_IFD, GPS_IFD_TAG_DOP, 0, wpt->hdop); + else exif_remove_tag(GPS_IFD, GPS_IFD_TAG_DOP); + + if WAYPT_HAS(wpt, speed) { + exif_put_str(GPS_IFD, GPS_IFD_TAG_SPEEDREF, "K"); + exif_put_double(GPS_IFD, GPS_IFD_TAG_SPEED, 0, MPS_TO_KPH(wpt->speed)); + } + else { + exif_remove_tag(GPS_IFD, GPS_IFD_TAG_SPEEDREF); + exif_remove_tag(GPS_IFD, GPS_IFD_TAG_SPEED); + } + + exif_write_apps(); /* Success, write the new file */ + + exif_success = 1; + } + } -#endif /**************************************************************************/ ff_vecs_t exif_vecs = { ff_type_file, - { - ff_cap_read /* waypoints */, - ff_cap_none /* tracks */, - ff_cap_none /* routes */ + { + ff_cap_read | ff_cap_write /* waypoints */, + ff_cap_none /* tracks */, + ff_cap_none /* routes */ }, exif_rd_init, - NULL, /* exif_wr_init, */ - exif_rd_deinit, - NULL, /* exif_wr_deinit, */ + exif_wr_init, + exif_rd_deinit, + exif_wr_deinit, exif_read, - NULL, /* exif_write */ + exif_write, NULL, exif_args, - CET_CHARSET_ASCII, 0 + CET_CHARSET_UTF8, 0 }; /**************************************************************************/ -- 2.30.2